﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tomahawk.Serialization;
using System.Xml.Serialization;
using Tomahawk.Serialization.SerialBoost;

namespace Tomahawk.Runtime.Timeline
{

    public abstract class Event
    {
        [XmlAttribute("Time")]
        public float time = 0.0f;

        [XmlAttribute("Label")]
        public string label = "";

        public abstract void Execute(SyncGroup group, float time);
    }

    public sealed class SyncGroup
    {

        public enum PlayMode
        {
            Stop,
            PlayForward,
            PlayBackward,
            External,
        }

        #region Serializable Attributes

        [XmlAttribute("Name")]
        public string name = "";

        [XmlElement("Tracks")]
        public SerializableList<BaseTrack> tracks = new SerializableList<BaseTrack>();
       
        [XmlElement("DirectorTrack")]
        public SerializableList<Event> director = new SerializableList<Event>();

        [XmlAttribute("Mode")]
        public PlayMode Mode 
        { 
            get { return mode; } 
            set 
            { 
                mode = value;
                foreach (KeyValuePair<string, BaseTrack> track in trackTable)
                {
                    track.Value.OnPlayModeChanged(mode);
                }
            } 
        }

        [XmlAttribute("TimeScale")]
        public float TimeScale { get { return timeScale; } set { timeScale = value; } }
        
        [XmlAttribute("Time")]
        public float Time { get { return time; } set { time = value; } }
        
        #endregion

        private Dictionary<string, BaseTrack> trackTable = new Dictionary<string, BaseTrack>();
        private float time = 0;
        private float lastTime = 0;
        private float timeScale = 1.0f;
        private PlayMode mode = PlayMode.Stop;

        [XmlIgnore()]
        public BaseTrack this[string index]
        {
            get
            {
                BaseTrack value = null;
                trackTable.TryGetValue(index, out value);
                return value;
            }
        }

        #region Events
        public delegate void OnEventDelegate(Event action);
        public event OnEventDelegate OnEvent = null;
        #endregion

        public SyncGroup() { }

        public SyncGroup(string name) { this.name = name; }

        public void Initialize()
        {
            foreach (BaseTrack track in tracks)
            {
                trackTable.Add(track.name, track);
            }
        }

        private int CompareEventsByTime(Event a, Event b)
        {
            return (int)Math.Floor(100 * (a.time - b.time));
        }

        public bool Connect(Timeline.FindTargetObject callback)
        {
            bool result = true;
            
            this.director.Sort(CompareEventsByTime);

            foreach (KeyValuePair<string, BaseTrack> track in trackTable)
            {
                Object target = callback(track.Value.targetName);
                result &= track.Value.Connect(target);
                track.Value.SortKeys();
                track.Value.OnPlayModeChanged(mode);
            }

            return result;
        }

        private void TriggerEvents(float startTime, float endTime)
        {
            int eventCount = this.director.Count;
            if (startTime < endTime)
            {
                for (int eventIdx = 0; eventIdx < eventCount; ++eventIdx)
                {
                    Event action = this.director[eventIdx];
                    if (action.time >= startTime && action.time < endTime)
                    {
                        action.Execute(this, this.time);
                        if (OnEvent != null) OnEvent(action);
                    }
                }
            }
            else
            {
                for (int eventIdx = eventCount - 1; eventIdx >= 0; --eventIdx)
                {
                    Event action = this.director[eventIdx];
                    if (action.time >= endTime && action.time < startTime)
                    {
                        action.Execute(this, this.time);
                        if (OnEvent != null) OnEvent(action);
                    }
                }
            }
        }

        public void Update(float elapsedTime)
        {
            switch (mode)
            {
                case PlayMode.Stop: return;
                case PlayMode.PlayForward: time += timeScale * elapsedTime; break;
                case PlayMode.PlayBackward: time -= timeScale * elapsedTime; break;
                case PlayMode.External: break;
            }

            TriggerEvents(lastTime, time);
            
            foreach (KeyValuePair<string, BaseTrack> track in trackTable)
            {
                track.Value.Update(time);
            }

            lastTime = time;
        }

        public T FindTrackByName<T>(string name) where T : BaseTrack
        {
            foreach (KeyValuePair<string, BaseTrack> track in trackTable)
            {
                if (string.Compare(track.Key, name, true) == 0)
                {
                    return track.Value as T;
                }
            }

            return null;
        }
    }

}
